package ar.edu.itba.pod.legajo45129.simul.communication;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.log4j.Logger;

import ar.edu.itba.pod.legajo45129.simul.communication.factory.MessageFactory;
import ar.edu.itba.pod.legajo45129.simul.communication.payload.ResourceTransferMessagePayloadImpl;
import ar.edu.itba.pod.simul.communication.Message;
import ar.edu.itba.pod.simul.communication.ThreePhaseCommit;
import ar.edu.itba.pod.simul.communication.payload.Payload;

public class ThreePhaseCommitImpl implements ThreePhaseCommit {
	private static Logger logger = Logger.getLogger(ThreePhaseCommitImpl.class);
	
	private enum ThreePhaseCommitState{START, CAN_COMMIT, PRE_COMMIT, DO_COMMIT};
	private ThreePhaseCommitState state;
	private ConnectionManagerImpl connectionManager;
	private String coordinator;
	private Timer timer;
	
	
	
	public ThreePhaseCommitImpl(ConnectionManagerImpl connectionManagerImpl) throws RemoteException {
		UnicastRemoteObject.exportObject(this, 0);
	
		connectionManager = connectionManagerImpl;
		state = ThreePhaseCommitState.START;
	}

	@Override
	public void abort() throws RemoteException {
		synchronized (state) {
			if (state.equals(ThreePhaseCommitState.START)) {
				logger.error("time out invoked before can commit");
				timer.cancel();
				throw new IllegalStateException("time out invoked before can commit");
				
			}
			
			state = ThreePhaseCommitState.START;
		}
		timer.cancel();
	}

	@Override
	public  boolean canCommit(String coordinatorId, long timeout)
			throws RemoteException {
		synchronized (state) {			
			if (!state.equals(ThreePhaseCommitState.START)) {
				return false;
			}
			
			coordinator = coordinatorId;
			
			timer = new Timer();
			timer.schedule(new ScheduleTask(), timeout);
			
			state = ThreePhaseCommitState.CAN_COMMIT;
			return true;
		}
	}

	@Override
	public void doCommit(String coordinatorId) throws RemoteException {
		synchronized (state) {
			if (!coordinator.equals(coordinatorId)) {
				logger.error("wrong coordinator");
				timer.cancel();
				throw new IllegalArgumentException("wrong coordinator, the coordinator must be: " + coordinator);
			}
			
			if (!state.equals(ThreePhaseCommitState.PRE_COMMIT)) {
				logger.error("wrong state");
				timer.cancel();
				throw new IllegalArgumentException("wrong state, the state must be: " + ThreePhaseCommitState.PRE_COMMIT);
			}
			
			String localNode = connectionManager.getNodeId().toString();
			TransactionManager tm = (TransactionManager) connectionManager.getNodeCommunication();
			ExchangeInfo exchangeInfo = tm.getExchangeInfo();
			Payload payload = new ResourceTransferMessagePayloadImpl(exchangeInfo);
			Message resourceTransferMessage = MessageFactory.ResourceTransfer(localNode, payload);
			
			logger.debug("send resource transfer message to " + localNode);
			connectionManager.getGroupCommunication().send(resourceTransferMessage, localNode);
			
			state = ThreePhaseCommitState.DO_COMMIT;
			
			timer.cancel();
		}
	}

	@Override
	public void onTimeout() throws RemoteException {
		logger.debug("time out on three phase commit");
		synchronized (state) {
			switch (state) {
			case START:
				throw new IllegalArgumentException("Method invoked before canCommit was invoked");
			case CAN_COMMIT:
				logger.debug("in CAN_COMMIT state, abort");
				abort();
				break;
			case PRE_COMMIT:
				logger.debug("in PRE_COMMIT state, do commit");
				doCommit(coordinator);
				break;
			case DO_COMMIT:
				logger.debug("in DO_COMMIT, reset state");
				state = ThreePhaseCommitState.START;
				break;
			}			
		}
		
	}

	@Override
	public void preCommit(String coordinatorId) throws RemoteException {
		synchronized (state) {
			if (!coordinator.equals(coordinatorId)) {
				logger.error("wrong coordinator");
				throw new IllegalArgumentException("wrong coordinator, the coordinator must be: " + coordinator);
			}
			
			if (!state.equals(ThreePhaseCommitState.CAN_COMMIT)) {
				logger.error("wrong state");
				throw new IllegalArgumentException("wrong state, the state must be: " + ThreePhaseCommitState.CAN_COMMIT);
			}
			
			state = ThreePhaseCommitState.PRE_COMMIT;			
		}
	}
	
	private class ScheduleTask extends TimerTask {

		@Override
		public void run() {
			try {
				onTimeout();
			} catch (RemoteException e) {
				throw new RuntimeException(e.getMessage());
			}
			
		}
	}

}
